package com.xyazm.address.service.impl;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.google.common.collect.Lists;
import com.xyazm.address.dao.AreaDAO;
import com.xyazm.address.dao.CityDAO;
import com.xyazm.address.dao.ProvinceDAO;
import com.xyazm.address.dao.StreetDAO;
import com.xyazm.address.domain.dto.AddressDTO;
import com.xyazm.address.domain.entity.AreaDO;
import com.xyazm.address.domain.entity.CityDO;
import com.xyazm.address.domain.entity.ProvinceDO;
import com.xyazm.address.domain.entity.StreetDO;
import com.xyazm.address.domain.param.QueryAddressParam;
import com.xyazm.address.exception.AddressErrorCodeEnum;
import com.xyazm.address.service.IAddressService;
import com.xyazm.common.exception.BaseBizException;
import com.xyazm.common.logger.LoggerFormat;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class AddressServiceImpl implements IAddressService {

    @Autowired
    private StreetDAO streetDAO;

    @Autowired
    private AreaDAO areaDAO;

    @Autowired
    private CityDAO cityDAO;

    @Autowired
    private ProvinceDAO provinceDAO;

    @Override
    public AddressDTO getAddress(QueryAddressParam query) {

        log.info(LoggerFormat.build()
                .remark("getAddress->request")
                .data("query",query)
                .finish());

        // 1、参数校验
        checkGetAddressParam(query);
        // 2、查询街道信息
        List<StreetDO> streetDOS = new ArrayList<>();
        if(streetNotEmpty(query)) {
            streetDOS = streetDAO.listStreets(query.getStreetCode(),query.getStreet());
            if(CollectionUtils.isEmpty(streetDOS)) {
                return null;
            }
        }

        //3、查询区信息
        List<AreaDO> areaDOS = new ArrayList<>();
        if(areaNotEmpty(query) || CollectionUtils.isNotEmpty(streetDOS)) {
            Set<String> areaCodes = Optional.ofNullable(streetDOS)
                    .map(street->street.stream().map(StreetDO::getAreaCode).collect(Collectors.toSet()))
                    .orElse(Collections.EMPTY_SET);
            if(StringUtils.isNotBlank(query.getAreaCode())) {
                areaCodes.add(query.getAreaCode());
            }
            areaDOS = areaDAO.listAreas(areaCodes,query.getArea());
            if(CollectionUtils.isEmpty(areaDOS)) {
                return null;
            }
        }

        //4、查询市信息
        List<CityDO> cities = new ArrayList<>();
        if(cityNotEmpty(query) || CollectionUtils.isNotEmpty(areaDOS)) {
            Set<String> cityCodes = Optional.ofNullable(areaDOS)
                    .map(area->area.stream().map(AreaDO::getCityCode).collect(Collectors.toSet()))
                    .orElse(Collections.EMPTY_SET);
            if(StringUtils.isNotBlank(query.getCityCode())) {
                cityCodes.add(query.getCityCode());
            }
            cities = cityDAO.listCities(cityCodes,query.getCity());
            if(CollectionUtils.isEmpty(cities)) {
                return null;
            }
        }

        //4、查询省信息
        List<ProvinceDO> provinceDOS = new ArrayList<>();
        if(provinceNotEmpty(query) || CollectionUtils.isNotEmpty(cities)) {
            Set<String> provinceCodes = Optional.ofNullable(cities)
                    .map(city->city.stream().map(CityDO::getProvinceCode).collect(Collectors.toSet()))
                    .orElse(Collections.EMPTY_SET);
            if(StringUtils.isNotBlank(query.getProvinceCode())) {
                provinceCodes.add(query.getProvinceCode());
            }
            provinceDOS = provinceDAO.listProvinces(provinceCodes,query.getProvince());
            if(CollectionUtils.isEmpty(provinceDOS)) {
                return null;
            }
        }

        //5、组装候选结果集合
        List<AddressDTO> candidates = assembleResult(provinceDOS,cities, areaDOS, streetDOS);

        //6、筛选结果
        for(AddressDTO candidate : candidates) {
            if(isMatch(candidate,query)) {
                //7、组装返参
                return candidate;
            }
        }
        return null;
    }

    /**
     * 参数校验
     * @author xuyou
     * 2022/6/4 0004 23:16
     * @param param
    */
    private void checkGetAddressParam(QueryAddressParam param) {
        if(allEmpty(param)) {
            throw new BaseBizException(AddressErrorCodeEnum.PARAM_CANNOT_ALL_EMPTY);
        }
    }

    private boolean allEmpty(QueryAddressParam query) {
        return StringUtils.isBlank(query.getProvince())
                && StringUtils.isBlank(query.getProvinceCode())
                && StringUtils.isBlank(query.getCity())
                && StringUtils.isBlank(query.getCityCode())
                && StringUtils.isBlank(query.getArea())
                && StringUtils.isBlank(query.getAreaCode())
                && StringUtils.isBlank(query.getStreet())
                && StringUtils.isBlank(query.getStreetCode());
    }


    private boolean provinceNotEmpty(QueryAddressParam query) {
        return StringUtils.isNotBlank(query.getProvince())
                || StringUtils.isNotBlank(query.getProvinceCode());
    }

    private boolean cityNotEmpty(QueryAddressParam query) {
        return StringUtils.isNotBlank(query.getCity())
                || StringUtils.isNotBlank(query.getCityCode());
    }

    private boolean areaNotEmpty(QueryAddressParam query) {
        return StringUtils.isNotBlank(query.getArea())
                || StringUtils.isNotBlank(query.getAreaCode());
    }

    private boolean streetNotEmpty(QueryAddressParam query) {
        return StringUtils.isNotBlank(query.getStreet())
                || StringUtils.isNotBlank(query.getStreetCode());
    }


    /**
     * 组装返参
     * @param provinceDOS
     * @param cities
     * @param areaDOS
     * @param streetDOS
     * @return
     */
    private List<AddressDTO> assembleResult(
            List<ProvinceDO> provinceDOS,
            List<CityDO> cities,
            List<AreaDO> areaDOS,
            List<StreetDO> streetDOS) {

        List<AddressDTO> result = new ArrayList<>();
        for(ProvinceDO provinceDO : provinceDOS) {
            result.addAll(assembleResult(provinceDO,cities, areaDOS, streetDOS));
        }

        return result;
    }


    private List<AddressDTO> assembleResult(ProvinceDO provinceDO, List<CityDO> cities, List<AreaDO> areaDOS
            , List<StreetDO> streetDOS) {

        //过滤出province下的city
        cities = cities.stream().filter(cityDO -> cityDO.getProvinceCode().equals(provinceDO.getCode()))
                .collect(Collectors.toList());

        if(CollectionUtils.isEmpty(cities)) {
            return Lists.newArrayList(new AddressDTO(provinceDO.getCode(), provinceDO.getName()));
        }

        List<AddressDTO> result = new ArrayList<>();
        for(CityDO cityDO : cities) {
            result.addAll(assembleResult(provinceDO, cityDO, areaDOS, streetDOS));
        }
        return result;
    }

    private List<AddressDTO> assembleResult(ProvinceDO provinceDO, CityDO cityDO, List<AreaDO> areaDOS
            , List<StreetDO> streetDOS) {

        //过滤出city下的area
        areaDOS = areaDOS.stream().filter(areaDO -> areaDO.getCityCode().equals(cityDO.getCode()))
                .collect(Collectors.toList());

        if(CollectionUtils.isEmpty(areaDOS)) {
            return Lists.newArrayList(new AddressDTO(provinceDO.getCode(), provinceDO.getName(),
                    cityDO.getCode(), cityDO.getName()));
        }

        List<AddressDTO> result = new ArrayList<>();
        for(AreaDO areaDO : areaDOS) {
            result.addAll(assembleResult(provinceDO, cityDO, areaDO, streetDOS));
        }
        return result;
    }

    private List<AddressDTO> assembleResult(ProvinceDO provinceDO, CityDO cityDO, AreaDO areaDO
            , List<StreetDO> streetDOS) {

        //过滤出area下的street
        streetDOS = streetDOS.stream().filter(streetDO -> streetDO.getAreaCode().equals(areaDO.getCode()))
                .collect(Collectors.toList());

        if(CollectionUtils.isEmpty(streetDOS)) {
            return Lists.newArrayList(new AddressDTO(provinceDO.getCode(), provinceDO.getName(),
                    cityDO.getCode(), cityDO.getName(), areaDO.getCode(), areaDO.getName()));
        }

        List<AddressDTO> result = new ArrayList<>();
        for(StreetDO streetDO : streetDOS) {
            result.add(new AddressDTO(provinceDO.getCode(), provinceDO.getName(),
                    cityDO.getCode(), cityDO.getName(), areaDO.getCode(), areaDO.getName(),
                    streetDO.getCode(), streetDO.getName()));
        }
        return result;
    }


    private boolean isMatch(AddressDTO candidate,QueryAddressParam query) {
        boolean provinceCodeMatch = doMatch(candidate.getProvinceCode(),query.getProvinceCode());
        boolean provinceMatch = doMatch(candidate.getProvince(),query.getProvince());
        boolean cityCodeMatch = doMatch(candidate.getCityCode(),query.getCityCode());
        boolean cityMatch = doMatch(candidate.getCity(),query.getCity());
        boolean areaCodeMatch = doMatch(candidate.getAreaCode(),candidate.getAreaCode());
        boolean areaMatch = doMatch(candidate.getArea(),query.getArea());
        boolean streetCodeMatch = doMatch(candidate.getStreetCode(),query.getStreetCode());
        boolean streetMatch = doMatch(candidate.getStreet(),query.getStreet());

        return provinceCodeMatch && provinceMatch && cityCodeMatch && cityMatch
                && areaCodeMatch && areaMatch && streetCodeMatch && streetMatch;
    }

    private boolean doMatch(String candidate,String query) {
        if(Objects.isNull(query) || com.baomidou.mybatisplus.core.toolkit.StringUtils.isBlank(query)) {
            return true;
        }
        if(Objects.isNull(candidate) || com.baomidou.mybatisplus.core.toolkit.StringUtils.isBlank(candidate)) {
            return false;
        }
        return candidate.equals(query);
    }


}
